import sys
import re

import magic

import utils

from settings import *
import settings


def get_dirs_and_files(path: str) -> list[list[str], list[str]]:
    dirs = []
    files = []
    
    for entry in os.scandir(path):
        if entry.is_dir():
            dirs.append(entry.name)
        elif entry.is_file():
            files.append(entry.name)

    return [dirs, files]


class Section:
    def __init__(self, x, y, width, height):
        '''
            section_type is one of ["preview", "ls"].
            relational links is supported.
        '''

        self.x = int(x)
        self.y = int(y)

        self.width = int(width)
        self.height = int(height)

        self.win = curses.newwin(self.height + 1, self.width + 1,
                                 self.y, self.x)

        self.path = os.getcwd()
        
        self.dirs, self.files = get_dirs_and_files(self.path)

        self.dirs.sort(key=str.casefold)
        self.files.sort(key=str.casefold)

        self.entries = self.dirs + self.files
        
        self.selected = self.entries[0]
        self.selected_index = 0

    def draw(self, update_left_section: bool = True, update_right_section: bool = True):
        self.win.clear()

        dirs = list(
            zip(self.dirs, [directory_color | directory_style] * len(self.dirs))
        )
        files = list(
            zip(self.files, [file_color | file_style] * len(self.files))
        )
        
        shift = 0
        if self.selected_index > self.height // 2:
            shift = self.selected_index - self.height // 2

        for ent_y, ent in enumerate((dirs + files)[shift:self.height + shift]):
            fullwidth_count = len(re.findall('[\\u3041-\\u3096\\u30A0-\\u30FF\\u3400-\\u4DB5\\u4E00-\\u9FCB\\uF900-\\uFA6A\\u2E80-\\u2FD5\\u3000-\\u303F\\u31F0-\\u31FF\\u3220-\\u3243\\u3280-\\u337F\\uFF01-\\uFF5E]', ent[0]))

            if fullwidth_count > 0:
                settings.cause_stdcsr_clear = True

            tail = ""
            is_reverse = 0
            if ent[0] == self.selected:
                tail = " "  * (self.width - len(ent[0]))
                is_reverse = curses.A_REVERSE

            self.win.addstr(
                ent_y, 0, ent[0][:self.width - fullwidth_count] + tail, ent[1] | is_reverse
            )

        self.win.refresh()
        
        if RightSection.instance and update_right_section:
            # RightSection.instance.update(
            #     self.selected
            # )
            RightSection.instance.draw(self.path, self.selected)
        
        if LeftSection.instance and update_left_section:
            LeftSection.instance.update(
                self.path.replace("\\", "/").split("/")[-1]
            )
            LeftSection.instance.draw(os.path.join(self.path, ".."))

    def update(self):
        if self.path != os.getcwd():
            self.path = os.getcwd()

            self.dirs.clear()
            self.files.clear()

            self.dirs, self.files = get_dirs_and_files(self.path)

            self.dirs.sort(key=str.casefold)
            self.files.sort(key=str.casefold)

            self.entries = self.dirs + self.files
            
            if self.selected in self.entries:
                self.selected_index = self.entries.index(self.selected)
            else:
                self.selected_index = 0

    def set_selected_index(self, new_val):
        if 0 <= new_val < len(self.entries):
            self.selected_index = new_val
            self.selected = self.entries[self.selected_index]

    def set_path(self, new_path):
        old_path = self.path
        self.path = os.path.abspath(new_path)
        self.path = utils.file_system.add_slash_to_path(self.path)

        self.entries = utils.file_system.directories_first(
                                utils.sorting.by_name(self.path), self.path)
        
        if (not self.selected is None) and len(old_path) > len(self.path):
            self.selected = old_path.split("/")[-3]
            self.selected_index = self.entries.index(self.selected)
        elif self.selected is None:
            self.selected_index = 0
            self.selected = self.entries[self.selected_index]


class RightSection:
    instance = None

    def __init__(self, x, y, width, height):
        self.add_instance(self)

        self.x = int(x)
        self.y = int(y)

        self.width = int(width)
        self.height = int(height)

        self.win = curses.newwin(self.height + 1, self.width + 1,
                                 self.y, self.x)

        self.path = ""
        self.selected = ""
        self.prev_selected = ""

    def draw(self, path: str, ent: str):
        """ Draw the contents of ent.

        ent - file name or directory name from where data will be showed.
        """
        
        if os.path.isfile(ent):
            try:
                with open(ent, "r") as f:
                    data = []
                    for i in range(self.height):
                        data.append(
                            re.sub(
                                f"[{non_printable_characters}]",
                                "?", f.readline()
                            ).replace("\t", "    ")
                        )
                        
                self.win.clear()
                for line_y, line in enumerate(data):
                    fullwidth_count = len(re.findall('[\\u3041-\\u3096\\u30A0-\\u30FF\\u3400-\\u4DB5\\u4E00-\\u9FCB\\uF900-\\uFA6A\\u2E80-\\u2FD5\\u3000-\\u303F\\u31F0-\\u31FF\\u3220-\\u3243\\u3280-\\u337F\\uFF01-\\uFF5E]', line))

                    if fullwidth_count > 0:
                        settings.cause_stdcsr_clear = True

                    self.win.addstr(
                        line_y, 0,
                        line[:self.width - fullwidth_count], 
                        file_color | file_style
                    )

                self.win.refresh()

            except UnicodeDecodeError:
                self.win.clear()
                self.win.addstr(
                    0, 0,
                    magic.from_buffer(open(ent, "rb").read(4096))[:self.width],
                    file_color | file_style | curses.A_REVERSE
                )
                self.win.refresh()

        elif os.path.isdir(ent):
            try:
                dirs, files = get_dirs_and_files(ent)
            except PermissionError:
                self.win.clear()
                self.win.addstr(
                    0, 0, "Cannot access the directory.",
                    warning_color | curses.A_REVERSE
                )
                self.win.refresh()
                return

            dirs.sort(key=str.casefold)
            files.sort(key=str.casefold)

            if self.selected not in dirs \
                and self.selected not in files:
                if dirs:
                    self.selected = dirs[0]
                elif files:
                    self.selected = files[0]
                else:
                    self.selected = ""

            dirs = list(
                zip(dirs, [directory_color | directory_style] * len(dirs))
            )
            files = list(
                zip(files, [file_color | file_style] * len(files))
            )
            
            self.win.erase()
            for ent_y, ent in enumerate((dirs + files)[:self.height]):
                tail = ""
                is_reverse = 0
                if ent[0] == self.selected:
                    tail = " "  * (self.width - len(ent[0]))
                    is_reverse = curses.A_REVERSE

                fullwidth_count = len(re.findall('[\\u3041-\\u3096\\u30A0-\\u30FF\\u3400-\\u4DB5\\u4E00-\\u9FCB\\uF900-\\uFA6A\\u2E80-\\u2FD5\\u3000-\\u303F\\u31F0-\\u31FF\\u3220-\\u3243\\u3280-\\u337F\\uFF01-\\uFF5E]', ent[0]))
                if fullwidth_count > 0:
                    settings.cause_stdcsr_clear = True

                self.win.addstr(
                    ent_y, 0, ent[0][:self.width - fullwidth_count] + tail,
                    ent[1] | is_reverse
                )

                # 2-1-1-2 = 6 (4)

            
            self.win.refresh()

    def update(self, selected: str):
        self.prev_selected, self.selected = self.selected, selected

    @classmethod
    def add_instance(cls, instance):
        if not cls.instance:
            cls.instance = instance


class LeftSection:
    instance = None

    def __init__(self, x, y, width, height):
        self.add_instance(self)

        self.x = int(x)
        self.y = int(y)

        self.width = int(width)
        self.height = int(height)

        self.win = curses.newwin(self.height + 1, self.width + 1,
                                 self.y, self.x)

        self.path = ""
        self.selected = ""

    def draw(self, path: str):
        """ Draw the contents of path.

        path - path which entries will be showed.
        """

        if sys.platform.startswith("win") \
            and re.fullmatch(".:/", os.getcwd().replace("\\", "/")):
            self.win.clear()
            self.win.refresh()
            return
        
        dirs = []
        files = []
        for obj in os.listdir(path):
            if os.path.isdir(os.path.join(path, obj)):
                dirs.append(obj)
            elif os.path.isfile(os.path.join(path, obj)):
                files.append(obj)

        dirs.sort(key=str.casefold)
        files.sort(key=str.casefold)

        dirs = list(
            zip(dirs, [directory_color | directory_style] * len(dirs))
        )
        files = list(
            zip(files, [file_color | file_style] * len(files))
        )

        self.win.clear()
        for ent_y, ent in enumerate((dirs + files)[:self.height]):
            tail = ""
            is_reverse = 0
            if ent[0] == self.selected:
                tail = " "  * (self.width - len(ent[0]))
                is_reverse = curses.A_REVERSE

            self.win.addstr(
                ent_y, 0,
                ent[0][:self.width] + tail, ent[1] | is_reverse
            )

        self.win.refresh()

    def update(self, selected: str):
        self.selected = selected

    @classmethod
    def add_instance(cls, instance):
        if not cls.instance:
            cls.instance = instance

